package priv.winnine.entity.vo;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Getter;
import lombok.Setter;
import lombok.SneakyThrows;
import priv.winnine.annotation.TimeStamp;
import priv.winnine.entity.bo.ErrorProperties;
import priv.winnine.entity.bo.InvalidParamDesc;
import priv.winnine.entity.enums.ResultEnum;
import priv.winnine.exception.ResultException;

import javax.validation.constraints.NotBlank;
import java.util.Map;
import java.util.regex.Pattern;

@Getter
@Setter
public class Request {

    private Map<String, Object> info;

    private Map<String, Object> subject;

    @TimeStamp(message = InvalidParamDesc.FORMAT_ERROR)
    @NotBlank(message = InvalidParamDesc.NOT_NULL)
    private String timestamp;

    private String data;

    public String getStringFromInfo(String key) {
        return getString(key, info);
    }

    public String getStringFromSubject(String key) {
        return getString(key, subject);
    }

    public Integer getIntFromInfo(String key) {
        return getInt(key, info);
    }

    public Integer getIntFromSubject(String key) {
        return getInt(key, subject);
    }

    public Boolean getBooleanFromInfo(String key) {
        return getBoolean(key, info);
    }

    public Boolean getBooleanFromSubject(String key) {
        return getBoolean(key, subject);
    }

    private String getString(String key, Map source) {
        Object o = source.get(key);
        if (o == null) {
            return null;
        }
        if (!(o instanceof String)) {
            throw new ResultException(ResultEnum.PROPERTY_ERROR, ErrorProperties.builder()
                    .property(key)
                    .message(InvalidParamDesc.ONLY_STRING)
                    .build());
        }
        if (((String) o).length() > 1000) {
            throw new ResultException(ResultEnum.PROPERTY_ERROR, ErrorProperties.builder()
                    .property(key)
                    .message(InvalidParamDesc.MAX_STRING)
                    .build());
        }
        return (String) o;
    }

    private Integer getInt(String key, Map source) {
        Object o = source.get(key);
        if (o == null || "".equals(o.toString())) {
            return null;
        }
        if (!isNumeric(o)) {
            throw new ResultException(ResultEnum.PROPERTY_ERROR, ErrorProperties.builder()
                    .property(key)
                    .message(InvalidParamDesc.ONLY_INTEGER)
                    .build());
        }
        if (!isInteger(o)) {
            throw new ResultException(ResultEnum.PROPERTY_ERROR, ErrorProperties.builder()
                    .property(key)
                    .message(InvalidParamDesc.MAX_INTEGER)
                    .build());
        }
//        if (!(o instanceof Integer)) {
//            throw new ResultException(ResultEnum.PROPERTY_ERROR, ErrorProperties.builder()
//                    .property(key)
//                    .message(InvalidParamDesc.ONLY_INTEGER)
//                    .build());
//        }
//        if ((Integer) o > Integer.MAX_VALUE) {
//            throw new ResultException(ResultEnum.PROPERTY_ERROR, ErrorProperties.builder()
//                    .property(key)
//                    .message(InvalidParamDesc.MAX_INTEGER)
//                    .build());
//        }
        return Integer.parseInt(o.toString());
    }

    private Boolean getBoolean(String key, Map source) {
        Object o = source.get(key);
        if (o == null || "".equals(o.toString())) {
            return null;
        }
        if (!isBoolean(o)) {
            throw new ResultException(ResultEnum.PROPERTY_ERROR, ErrorProperties.builder()
                    .property(key)
                    .message(InvalidParamDesc.ONLY_BOOLEAN)
                    .build());
        }
        return Boolean.valueOf(o.toString());
    }

    @SneakyThrows
    @Override
    public String toString() {
        return new ObjectMapper().writeValueAsString(this);
    }

    private static boolean isNumeric(Object object) {
        Pattern pattern = Pattern.compile("^[-\\+]?[\\d]*$");
        return pattern.matcher(object.toString()).matches();
    }

    private static boolean isInteger(Object object) {
        try {
            Integer.parseInt(object.toString());
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    private static boolean isBoolean(Object object) {
        return "true".equalsIgnoreCase(object.toString()) || "false".equalsIgnoreCase(object.toString());
    }

}
